ZYNQ-7000 — это SoC на базе архитектуры ARM Cortex-A9 + FPGA. Его процесс загрузки управляется аппаратно (со стороны PS) и поэтапно выполняет запуск системы и конфигурацию программируемой логики. Процесс загрузки для SoC ZYNQ 7000 — это многоэтапная, гибко настраиваемая процедура, включающая сложные взаимодействия между автоматической аппаратной загрузкой и программным управлением. В этой статье будет представлен подробный анализ всего процесса, от включения питания до загрузки операционной системы.
PS (ARM) и PL (FPGA) сбрасываются.
Состояние выводов BOOT_MODE считывается для определения носителя загрузки.
PL остаётся в несконфигурированном состоянии.
Последовательность POR (Power-On Reset)
Все шины питания достигают стабильного состояния (сигнал PS_POR_B переходит в высокое состояние)
Запускается внутренний генератор (типичная частота 33.33MHz)
Делители частоты устанавливаются в значения по умолчанию
Конфигурация сети тактирования
x// Начальная конфигурация тактирования в BootROM
// Типичные значения для инициализацииwritel(ARM_PLL_CTRL, 0x0001A008); // Установить PLL в режим обхода (bypass)Режим загрузки определяется состоянием выводов MIO[5:2]:
| MIO[5:2] | Режим загрузки | Тактовая частота | Ширина данных |
|---|---|---|---|
| 0000 | JTAG | - | - |
| 0010 | NAND | 33MHz | 8-bit |
| 0011 | NOR | 50MHz | 16-bit |
| 0101 | QSPI | 100MHz | 4-bit |
| 0110 | SD | 50MHz | 4-bit |
Примечание: Состояние выводов режима считывается и фиксируется по нарастающему фронту сигнала POR_B
Код BootROM — это первое программное обеспечение, которое выполняется на устройстве Zynq после включения питания или сброса. Он начинает выполнение с адреса 0xFFFF_0000. BootROM недоступен для пользователя. Код BootROM хранится во встроенном ПЗУ (ROM), отсюда и название BootROM. Поскольку ZYNQ содержит 256КБ ОЗУ (RAM) и 128КБ ПЗУ (ROM), код BootROM может постоянно храниться в ПЗУ и не будет утерян при отключении питания. Обычно внутреннее ПЗУ чипа — это NOR Flash. Ключевой особенностью NOR Flash является eXecute In Place (XIP), что позволяет приложениям выполняться непосредственно из Flash-памяти без необходимости загрузки в системное ОЗУ. Это ПЗУ, встроенное в чип, и его нельзя изменить.
Инициализирует внутреннее оборудование PS.
Считывает заголовок BOOT.BIN и FSBL с загрузочного носителя.
Считывает состояние выводов режима загрузки для определения загрузочного устройства.
Инициализирует тактовые генераторы и периферийные устройства.
Считывает загрузочный образ с выбранного загрузочного устройства.
Анализирует заголовок образа и проверяет его корректность.
Загружает раздел FSBL в OCM.
Передает управление FSBL.
Инициализирует выводы MIO, в основном настраивая регистры конфигурации физических характеристик для выводов MIO. Ключевым шагом является мультиплексирование MIO40~MIO45 для выполнения функций выводов CLK/CMD/DATA для периферийного устройства SD0.
Инициализирует периферийное устройство SD-карты и его драйвер, обеспечивая возможность операций чтения/записи на SD-карту.
Тестирует функциональность чтения/записи SD-карты.
Считывает файл BOOT.BIN из файловой системы SD-карты и анализирует заголовок BootROM. Файлу BOOT.BIN предшествует секция заголовка, организованная в определенном формате. Этот заголовок содержит такую информацию, как адрес загрузки FSBL, его размер и смещение внутри файла BOOT.BIN. Код BootROM способен анализировать этот заголовок.
Получив размер, смещение и адрес загрузки кода FSBL, код BootROM копирует код FSBL из файла BOOT.BIN в ОЗУ, а затем переходит по адресу выполнения FSBL для его запуска.
На этом этапе BootROM успешно запустил код FSBL.
Последовательность выполнения кода BootROM в режиме загрузки с QSPI выглядит следующим образом:
Инициализация выводов MIO, мультиплексирование соответствующих выводов MIO для выполнения функций, необходимых периферии QSPI.
Инициализация периферии QSPI и её драйвера для устройства QSPI Flash, обеспечение возможности операций чтения/записи QSPI.
Тестирование функциональности чтения/записи QSPI.
Чтение файла BOOT.BIN с носителя QSPI и разбор заголовка Boot ROM.
После получения размера кода FSBL, смещения и адреса загрузки, код BootROM копирует код FSBL из файла BOOT.BIN в RAM и затем переходит по адресу выполнения FSBL для его запуска.
В отличие от метода поиска файловой системы, используемого для SD-карт, в режиме загрузки с QSPI код BootROM сначала ищет файл BOOT.BIN по адресу 0x000000 в QSPI. Если файл не найден, поиск продолжается по адресу 0x008000. Если и там файл не найден, переход осуществляется к следующему адресу 0x10000. Однако диапазон поиска не должен превышать первые 16 МБ адресного пространства QSPI.
BOOT.BIN — это основной файл образа загрузки для серии SoC ZYNQ 7000. (Образ загрузки — это упакованный файл, содержащий несколько программ или данных, необходимых для запуска системы, обычно двоичный файл, используемый для руководства системой через весь процесс от включения питания до запуска операционной системы. В Zynq образом загрузки обычно является файл BOOT.BIN. BOOT.BIN — это центральный файл для всей процедуры запуска системы и должен быть размещен в начале загрузочного устройства Zynq. Он считывается и выполняется кодом загрузки ROM Zynq из QSPI, SD-карты или NAND.) Он генерируется инструментом Bootgen на основе файла .bif и содержит все компоненты, необходимые для полной цепочки загрузки. Например:
xxxxxxxxxxbifthe_ROM_image:{ [bootloader] fsbl.elf // Partition 1: FSBL, must be first system.bit // Partition 2: FPGA configuration file (optional) u-boot.elf // Partition 3: Second-stage bootloader}В приведенном выше примере:
| Порядок раздела | Содержание | Назначение |
|---|---|---|
| 1 | fsbl.elf | Первичный загрузчик, инициализирует оборудование и DDR |
| 2 | system.bit | Конфигурирует PL (FPGA часть) |
| 3 | u-boot.elf | Вторичный загрузчик, запускает Linux или другие приложения |
xxxxxxxxxx┌───────────────────────┐│ Boot Header │ → Заголовок фиксированного формата (256 байт)├───────────────────────┤│ Partition Header Table│ → Описания разделов (20 байт/запись)├───────────────────────┤│ FSBL (Required) │ → Первичный загрузчик├───────────────────────┤│ Bitstream (Optional)│ → Данные конфигурации PL├───────────────────────┤│ SSBL/App (Optional) │ → U-Boot/ядро Linux, приложения и т.д.├───────────────────────┤│ Auth Cert (Optional) │ → RSA подпись/AES зашифрованные данные└───────────────────────┘
Заголовок BOOT.BIN — это секция данных в начале файла BOOT.BIN, организованная в определенном формате, который может быть проанализирован кодом BootROM. BootROM считывает Заголовок Загрузки из образа загрузки, чтобы получить информацию об образе, такую как:
Стартовый адрес FSBL.
Источник ключа шифрования.
Размер FSBL.
Смещение FSBL.
Контрольную сумму для проверки целостности заголовка.
Заголовок Загрузки должен существовать и быть правильно отформатирован. В противном случае BootROM не загрузит FSBL.
В файле BOOT.bin диапазон адресов от 0 до 0x8FF можно разделить на 17 частей, каждая из которых имеет определенное значение.
0x000: Таблица векторов прерываний.
0x020: Фиксированное значение 0xaa995566 (little-endian).
0x024: Фиксированное значение 0x584c4e58 ASCII: XLNX.
0x028: Если значение равно 0xa5c3c5a3 или 0x3a5c3c5a, образ зашифрован.
0x02C: Номер версии заголовка BootROM, можно игнорировать.
0x030: Этот параметр содержит количество байт от начала действительного заголовка BootROM до местоположения образа FSBL/пользовательского кода, которое является смещением адреса FSBL/пользовательского кода. Это смещение должно быть больше или равно 0x8C0.
0x034: Записывает длину FSBL, используется для руководства кодом BootROM при копировании FSBL.
0x038: Адрес загрузки, указывающий, куда копировать FSBL в OCM (обычно 0x0). Он направляет код BootROM, куда копировать FSBL в RAM.
0x03C: Адрес выполнения FSBL в OCM (обычно определяется как 0x0). Он направляет код BootROM, по какому адресу RAM переходить для выполнения.
0x040: Записывает длину FSBL.
0x044: Фиксированное значение 0x01.
0x048: Контрольная сумма (Вычисляется путем суммирования данных между 0x020 и 0x047 как 32-битных слов с последующим взятием побитового НЕ. Если сумма превышает 32 бита, для операции НЕ используются только младшие 32 бита).
0x04C-0x097: Пользовательская область для FSBL/пользовательского кода. Можно заполнить нулями, если не нужна.
0x098: Смещение до таблицы заголовков образов.
0x09C: Расположение таблицы заголовков разделов.
0x0A0-0x89F: Параметры для инициализации регистров.
17.0x8C0: FSBL и пользовательский код должны располагаться по этому адресу или после него.
Длину FSBL можно посмотреть по адресу 0x034:
Адрес входа в таблицу векторов прерываний можно посмотреть по адресам 0x000-0x01F:
Фиксированное значение можно посмотреть по адресу 0x020:
Начальный указатель PC
ARM Cortex-A9 начинает выполнение с адреса 0x00000000
Первые 64 КБ — это образ BootROM (только для чтения)
Инициализация контроллера DDR
Конфигурация мультиплексирования выводов MIO
Настройка системных тактовых частот
Включение контроллера прерываний
FSBL — это загрузчик первого этапа (First Stage Bootloader) в процессе загрузки Zynq, который напрямую загружается и выполняется BootROM. Его основные функции включают:
Инициализация оборудования: Настройка базовой периферии, такой как память DDR, тактовые генераторы и MIO (Multi-use I/O).
Разбор BOOT.BIN: Чтение таблицы заголовков разделов (Partition Header Table) из образа для идентификации различных разделов (например, Bitstream, SSBL, приложение).
Загрузка последующих образов: Копирование программы второго этапа (например, U-Boot, bare-metal приложение, ядро Linux) по указанному адресу в памяти.
Опциональная загрузка конфигурации PL: Если существует Bitstream для ПЛИС, FSBL загрузит его в секцию PL (программируемая логика).
Сценарий 1: Bare-metal приложение (Standalone)
Структура BOOT.BIN:
xxxxxxxxxxBOOT.BIN = FSBL + Bitstream (optional) + Application (.elf)
Процесс:
BootROM загружает FSBL во встроенную память (OCM, On-Chip Memory) и выполняет его.
FSBL инициализирует DDR и загружает приложение (например, application.elf) из Flash/QSPI/SD-карты по указанному адресу в DDR (определенному в скрипте компоновщика, например, 0x00100000).
FSBL переходит к точке входа приложения (обычно _start или main).
Сценарий 2: Запуск Linux (через U-Boot)
Структура BOOT.BIN:
xxxxxxxxxxBOOT.BIN = FSBL + Bitstream (optional) + U-Boot
Процесс:
FSBL загружает U-Boot (SSBL) в DDR.
U-Boot продолжает загрузку ядра Linux (uImage), дерева устройств (.dtb) и корневой файловой системы (например, rootfs.cpio) в память.
U-Boot запускает ядро. На этом этапе FSBL завершил свою работу и больше не участвует в последующем процессе.
Сценарий 3: Прямая загрузка Linux (без U-Boot)
В редких случаях FSBL может напрямую загружать ядро Linux (которое должно быть сконфигурировано в формате kernel.img), но обычно рекомендуется использовать U-Boot в качестве посредника.
Без ОС: FSBL переходит непосредственно к приложению и не возвращается.
С U-Boot: FSBL отвечает только за загрузку U-Boot, который затем управляет последующим процессом загрузки.
Процесс загрузки Bitstream:
Передается через интерфейс PCAP
Ускоряется с помощью DMA (типичная пропускная способность 400 Мбит/с)
Проверка статуса:
xxxxxxxxxx
void load_bitstream(uint32_t* addr, uint32_t size) { writel(PCAP_PROG_B, 0x0); // Assert PROG_B delay(100); writel(PCAP_PROG_B, 0x1); // De-assert PROG_B while(size > 0) { uint32_t data = *addr++; writel(PCAP_DATA, data); size -= 4; while(readl(PCAP_STATUS) & 0x4); // Wait for DMA to be ready }}| Размер Bitstream | Время конфигурации (100 МГц) |
|---|---|
| 1MB | 80ms |
| 5MB | 400ms |
| 10MB | 800ms |
Файл BOOT.BIN содержит образ FSBL, образ u-boot и файл bitstream.
Код BootROM должен найти FSBL, разобрав информацию в заголовке BOOT.BIN. Затем код BootROM запускает FSBL.
После запуска кода FSBL он отвечает за поиск образа U-Boot и файла bitstream в файле BOOT.BIN, затем за загрузку файла bitstream в PL-часть ZYNQ и, наконец, за запуск U-Boot.
Это включает в себя три таблицы данных:
таблица заголовков образов;
таблица заголовков разделов;
заголовок образа.
Существует только одна таблица заголовков образов. Таблица заголовков разделов и заголовок образа идут парами. Количество образов, содержащихся в файле BOOT.BIN, определяет количество пар "таблица заголовков разделов - заголовок образа".
0x00: Номер версии таблицы заголовков образов;
0x04: Количество заголовков образов;
0x08: Смещение позиции первой таблицы заголовков разделов (Partition Header table). Рассчитывается в словах, поэтому фактическое смещение нужно умножить на 4;
0x0C: Смещение позиции первого заголовка образа (Image Header). Измеряется в словах;
0x10: Смещение аутентификации заголовка. Измеряется в словах;
0x1C: Дополняется значением 0xFFFFFFFF до тех пор, пока вся таблица заголовков образов не достигнет размера 64 байта.
0x0: Смещение адреса следующего заголовка образа. Если здесь 0, это означает, что это последний заголовок образа;
0x4: Смещение позиции связанной таблицы заголовков разделов;
0x8: Этот адрес всегда равен 0;
0xC: Значение фактического количества разделов;
0x10-N: Записывает имя образа.
переменный: Используется для выравнивания.
0x0: Длина данных зашифрованного раздела; единица измерения — слова, поэтому при вычислении необходимо умножить на 4;
0x4: Длина данных незашифрованного раздела. Если этот раздел — u-boot, это поле указывает длину u-boot, вычисляется так же;
0x8: Общая длина зашифрованных данных + выравнивание + расширение + данные аутентификации;
0xC: Адрес загрузки данных этого раздела, указывающий, куда в память следует скопировать данные;
0x10: Адрес выполнения данных этого раздела, указывающий адрес памяти для перехода при выполнении кода этого раздела;
0x14: Смещение позиции данных этого раздела внутри файла BOOT.BIN. Копирование начинается с этого адреса;
0x18: Биты атрибутов;
0x1C: Количество секций;
0x20: Позиция поля контрольной суммы;
0x24: Позиция заголовка образа, соответствующего этой таблице заголовков разделов. В единицах слов;
0x28: Поля, связанные с шифрованием;
0x2C-0x38: Не определено;
0x3C: Контрольная сумма.
Функция: Описывает глобальные атрибуты всего загрузочного образа.
Типичные поля:
xxxxxxxxxxtypedef struct { uint32_t magic; // Magic number (e.g., "XNLX") uint32_t version; // Image version uint32_t partition_num; // Number of partitions uint32_t header_checksum;// Header checksum} ImageHeader;Место хранения: Начало файла образа.
Функция: Индексирует метаданные всех разделов (а не сами данные разделов).
Содержимое:
offset, size и load_addr каждого раздела
Указатель на сертификат аутентификации раздела
Пример:
xxxxxxxxxxtypedef struct { uint32_t partition_offset; // Offset of the partition data within the image uint32_t partition_size; uint32_t load_address; // Address to load into memory uint32_t attribute_word; // Partition attributes (encryption/authentication, etc.)} ImageHeaderTableEntry;Функция: Описывает подробные атрибуты одного раздела.
Типичные поля:
xxxxxxxxxxtypedef struct { uint32_t exec_address; // Execution entry address uint32_t auth_cert_offset; // Authentication certificate offset uint8_t hash[32]; // Partition hash value uint8_t reserved[16];} PartitionHeaderTable;Ключевые отличия:
Каждый раздел имеет свою собственную независимую таблицу заголовков раздела
Содержит информацию о безопасности, специфичную для раздела (например, хэш, сертификат)
BootROM считывает Image Header для проверки целостности образа.
FSBL анализирует Image Header Table для нахождения всех разделов.
Для каждого раздела:
Проверить легитимность раздела (RSA/хэш) через Partition Header Table.
Загрузить его в память по адресу, указанному в Image Header Table.
FSBL — это первый программируемый пользователем код, выполняемый после включения питания платформы ZYNQ. Он в основном отвечает за инициализацию оборудования и загрузку загрузчика следующего этапа. Его основная архитектура выглядит следующим образом:
main())xxxxxxxxxxint main(void) { // 1. PS7 initialization (MIO/PLL/clocks/DDR) Status = ps7_init(); // 2. Unlock SLCR registers SlcrUnlock(); // 3. Cache handling Xil_DCacheFlush(); Xil_DCacheDisable(); // 4. Register exception handlers RegisterHandlers(); // 5. Peripheral initialization (based on boot mode) switch(BootModeRegister) { case QSPI_MODE: InitQspi(); case NAND_MODE: InitNand(); case SD_MODE: InitSD(); // ... } // 6. Load boot image HandoffAddress = LoadBootImage(); // 7. Hand off control FsblHandoff(HandoffAddress);}LoadBootImage())xxxxxxxxxxu32 LoadBootImage(void) { // 1. Get boot status and multi-boot register values RebootStatusRegister = Xil_In32(REBOOT_STATUS_REG); MultiBootReg = Xil_In32(XDCFG_MULTIBOOT_ADDR_OFFSET); // 2. Get image start address ImageStartAddress = ComputeImageStart(); // 3. Get partition header info Status = GetPartitionHeaderInfo(ImageStartAddress); // 4. Partition processing loop while (PartitionNum < PartitionCount) { // - Validate partition header Status = ValidateHeader(HeaderPtr); // - Load partition data Status = PartitionMove(ImageStartAddress, HeaderPtr); // - Security verification (RSA/checksum) if (SignedPartitionFlag) { Status = AuthenticatePartition(...); } // - PL bitstream handling if (PLPartitionFlag) { Status = PcapLoadPartition(...); } PartitionNum++; } return ExecAddress; // Return handoff address}PartHeader)xxxxxxxxxxtypedef struct { u32 image_word_len; // Partition length in the image (in words) u32 data_word_len; // Actual data length (in words) u32 partition_word_len;// Total partition length (in words) u32 load_addr; // Load address (DDR) u32 exec_addr; // Execution address u32 partition_offset; // Partition offset in the image u32 attribute; // Attribute word (encryption/signature, etc.) u32 checksum; // Partition header checksum} PartHeader;xxxxxxxxxx// PL bitstream partition// PS code partition// RSA signature present// Partition ownerxxxxxxxxxx// RSA signature verificationStatus AuthenticatePartition(u8 *PartitionStart, u32 Length) { // 1. Verify signature if (XSecure_RsaVerify(...) != XST_SUCCESS) { return XST_FAILURE; } // 2. Check hash if (ValidateHash(...) != XST_SUCCESS) { return XST_FAILURE; } return XST_SUCCESS;}
// Decrypt encrypted partitionStatus DecryptPartition(u32 Addr, u32 DataLen, u32 ImgLen) { // Decrypt using AES engine XDcfg_AesDecryptxxxxxxxxxx0x00000000 - 0x0003FFFF: BootROM + FSBL0x00100000 - 0x001FFFFF: U-Boot0x00200000 - 0x002FFFFF: Device Tree0x03000000 - 0x04000000: Linux Kernel0x08000000 - : Root File Systemxxxxxxxxxxsetenv bootargs 'console=ttyPS0,115200 root=/dev/mmcblk0p2 rw earlyprintk'setenv loadaddr 0x03000000setenv fdtaddr 0x00200000bootm ${loadaddr} - ${fdtaddr}Использование BBRAM или eFUSE для хранения ключа
Режим CBC, 256-битный ключ
Независимый IV для каждого раздела
xxxxxxxxxxvoid set_multiboot(uint32_t offset) { uint32_t val = (offset >> 17) & 0x3FF; // Convert to 128KB blocks writel(MULTIBOOT_REG, val | 0x1); // Enable bit}xxxxxxxxxx0x000000: Golden Image (Backup)0x100000: Primary Image v1.00x200000: Primary Image v2.00x300000: Diagnostic Image| Симптом | Возможная причина | Решение |
|---|---|---|
| Зависание на этапе BootROM | Сбой определения загрузочного устройства | Проверьте конфигурацию выводов MIO |
| Сбой загрузки FSBL | Недостаточно места в OCM | Оптимизируйте размер FSBL до <192KB |
| Тайм-аут конфигурации PL | Тактовый сигнал интерфейса PCAP не включен | Проверьте конфигурацию регистра SLCR |
Включите передачу через QSPI DMA (установите QSPI_CR[3]=1)
Запускайте FSBL в режиме XIP (Execute-In-Place)
Сжимайте bitstream (через BITSTREAM.CONFIG.COMPRESS)
xxxxxxxxxx0xF8000000 - System-Level Control Registers0xF8000B00 - MIO Pin Control0xF8000D00 - Clock Controlxxxxxxxxxx0xF8007000 - PCAP Control0xF8007030 - Security Configuration0xF80070A0 - Multiboot ControlUG585: Техническое справочное руководство по Zynq-7000
UG821: Руководство разработчика программного обеспечения для Zynq-7000
UG873: Учебное пособие по встраиваемому дизайну
XAPP1175: Безопасная загрузка СнК Zynq-7000